package com.huixh.train.business.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.huixh.train.business.domain.*;
import com.huixh.train.business.dto.ConfirmOrderMqDto;
import com.huixh.train.business.enums.ConfirmOrderStatusEnum;
import com.huixh.train.business.enums.SeatColEnum;
import com.huixh.train.business.enums.SeatTypeEnum;
import com.huixh.train.business.mapper.ConfirmOrderMapper;
import com.huixh.train.business.req.ConfirmOrderDoReq;
import com.huixh.train.business.req.ConfirmOrderQueryReq;
import com.huixh.train.business.req.ConfirmOrderTicketReq;
import com.huixh.train.business.resp.ConfirmOrderQueryResp;
import com.huixh.train.business.service.*;
import com.huixh.train.common.enums.LockPreEnum;
import com.huixh.train.common.exception.BusinessException;
import com.huixh.train.common.exception.BusinessExceptionEnum;
import com.huixh.train.common.resp.PageResult;
import com.huixh.train.utils.random.SnowUtil;
import jakarta.annotation.Resource;
import org.apache.http.client.utils.DateUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.BiConsumer;

/**
 * @author huixh
 */
@Service
public class ConfirmOrderServiceImpl implements ConfirmOrderService {

    private final Logger logger = LoggerFactory.getLogger(ConfirmOrderServiceImpl.class);
    @Resource
    private ConfirmOrderMapper confirmOrderMapper;
    @Resource
    private DailyTrainTicketService dailyTrainTicketService;
    @Resource
    private SnowUtil snowUtil;
    @Resource
    private DailyTrainCarriageService dailyTrainCarriageService;
    @Resource
    private DailyTrainSeatService dailyTrainSeatService;
    @Resource
    private AfterConfirmOrderServiceImpl afterConfirmOrderService;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public void save(ConfirmOrderDoReq confirmOrderDoReq) {
        DateTime now = DateTime.now();
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(confirmOrderDoReq, ConfirmOrder.class);
        if (ObjectUtil.isNull(confirmOrder.getId())) {
            confirmOrder.setId(snowUtil.getSnowflakeToLong());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.insert(confirmOrder);
        } else {
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.updateByPrimaryKey(confirmOrder);
        }
    }

    @Override
    public PageResult<List<ConfirmOrderQueryResp>> queryList(ConfirmOrderQueryReq confirmOrderQueryReq) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.setOrderByClause("id desc");
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();

        logger.info("查询页码：{}", confirmOrderQueryReq.getPageNum());
        logger.info("每页条数：{}", confirmOrderQueryReq.getPageSize());
        PageHelper.startPage(confirmOrderQueryReq.getPageNum(), confirmOrderQueryReq.getPageSize());
        List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
        logger.info("总行数：{}", pageInfo.getTotal());
        logger.info("总页数：{}", pageInfo.getPages());

        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
        return PageResult.success(pageInfo.getTotal(), list);
    }

    @Override
    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    /**
     * 预扣减库存方法
     * 本方法用于在购票操作前预先扣减指定车次的座位类型库存。
     * 如果扣减后库存小于0，则抛出业务异常，表示票数不足。
     *
     * @param ticket  DailyTrainTicket对象，代表待扣减库存的车次信息。
     * @param tickets List<ConfirmOrderTicketReq>对象，包含所有购票请求的信息。
     * @throws BusinessException 如果扣减库存后任一座位类型票数不足，则抛出此异常。
     */
    private void deductTicketStock(DailyTrainTicket ticket, List<ConfirmOrderTicketReq> tickets) throws BusinessException {
        Map<SeatTypeEnum, BiConsumer<DailyTrainTicket, Integer>> stockDeductionActions = new HashMap<>(8);
        stockDeductionActions.put(SeatTypeEnum.YDZ, (t, c) -> {
            int remaining = t.getYdz() - c;
            if (remaining < 0) {
                throw new BusinessException(BusinessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH);
            }
            t.setYdz(remaining);
        });
        stockDeductionActions.put(SeatTypeEnum.RW, (t, c) -> {
            int remaining = t.getRw() - c;
            if (remaining < 0) {
                throw new BusinessException(BusinessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH);
            }
            t.setRw(remaining);
        });
        stockDeductionActions.put(SeatTypeEnum.EDZ, (t, c) -> {
            int remaining = t.getEdz() - c;
            if (remaining < 0) {
                throw new BusinessException(BusinessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH);
            }
            t.setEdz(remaining);
        });
        stockDeductionActions.put(SeatTypeEnum.YW, (t, c) -> {
            int remaining = t.getYw() - c;
            if (remaining < 0) {
                throw new BusinessException(BusinessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH);
            }
            t.setYw(remaining);
        });

        for (ConfirmOrderTicketReq ticketReq : tickets) {
            SeatTypeEnum seatType = EnumUtil.getBy(SeatTypeEnum::getCode, ticketReq.getSeatTypeCode());
            if (seatType == null) {
                continue; // 或者抛出异常，根据业务需求决定
            }

            stockDeductionActions.getOrDefault(seatType, (__, ___) -> {
            }).accept(ticket, 1);
        }
    }


    @Override
    public void doConfirm(ConfirmOrderMqDto confirmOrderDoReq) {
        String confirmOrderLock = LockPreEnum.LOCK_PRE_CONFIRM_ORDER.getPrefix()
                + DateUtils.formatDate(confirmOrderDoReq.getDate())
                + confirmOrderDoReq.getTrainCode();
        RLock lock = redissonClient.getLock(confirmOrderLock);

        if (lock.tryLock()) {
            logger.info("抢到购票锁 lockKey：{}", confirmOrderLock);
        } else {
            logger.info("其他线程正在销售···");
            return;
        }

        try {
            while (true) {
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                confirmOrderExample.createCriteria().andDateEqualTo(confirmOrderDoReq.getDate())
                        .andTrainCodeEqualTo(confirmOrderDoReq.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1,5);
                List<ConfirmOrder> confirmOrders = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);
                PageHelper.clearPage();
                if (CollUtil.isEmpty(confirmOrders)) {
                    logger.info("没有需要处理的订单，执行下次任务。");
                    break;
                } else {
                    logger.info("本次处理【{}】条订单", confirmOrders.size());
                }

                confirmOrders.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getBusinessExceptionEnum().getDesc().equals(BusinessExceptionEnum.BUSINESS_TICKET_NOT_ENOUGH.getDesc())) {
                            logger.info("本订单余票不足，处理下一订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        } else {
                            throw e;
                        }
                    }
                });
            }
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }

    public void updateStatus(ConfirmOrder confirmOrder) {
        ConfirmOrder updateConfirmOrder = new ConfirmOrder();
        updateConfirmOrder.setId(confirmOrder.getId());
        updateConfirmOrder.setStatus(confirmOrder.getStatus());
        updateConfirmOrder.setUpdateTime(new Date());
        confirmOrderMapper.updateByPrimaryKeySelective(updateConfirmOrder);
    }


    public void getSeat(List<DailyTrainSeat> finalChooseSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offsets,
                        Integer start, Integer end) {
        List<DailyTrainSeat> getSeatList = new ArrayList<>();
        // 一个车厢一个车厢获取座位数据 查询出当天这趟车次的车厢信息
        List<DailyTrainCarriage> dailyTrainCarriages = dailyTrainCarriageService.selectBySeatType(seatType, date, trainCode);
        logger.info("共查：{}条车厢数据", dailyTrainCarriages.size());

        for (DailyTrainCarriage dailyTrainCarriage : dailyTrainCarriages) {
            logger.info("车厢：{}开始选座", dailyTrainCarriage);
            // 查询当天当日车次 从1车厢逐一匹配
            List<DailyTrainSeat> dailyTrainSeats = dailyTrainSeatService.selectByCarriage(date, trainCode, dailyTrainCarriage.getIndex());
            logger.info("共查：{}条座位数据", dailyTrainSeats.size());

            for (DailyTrainSeat dailyTrainSeat : dailyTrainSeats) {

                String col = dailyTrainSeat.getCol();
                Integer carriageSeatIndex = dailyTrainSeat.getCarriageSeatIndex();

                // 判断当前座位未被选择
                boolean alreadyChoose = false;
                for (DailyTrainSeat trainSeat : finalChooseSeatList) {
                    if (trainSeat.getId().equals(dailyTrainSeat.getId())) {
                        alreadyChoose = true;
                        break;
                    }
                }

                if (alreadyChoose) {
                    logger.info("座位：{}已被选择，跳过", dailyTrainSeat.getCarriageSeatIndex());
                    continue;
                }

                // 判断 column 是否为空，为空则表示没有选座
                if (StrUtil.isBlank(column)) {
                    logger.info("无选座");
                } else {
                    if (!col.equals(column)) {
                        logger.info("座位{}列值不存在，继续判断下一座位，当前列值：{}，目标列值：{}", carriageSeatIndex, col, column);
                        continue;
                    }
                }

                boolean isChoose = calSell(dailyTrainSeat, start, end);
                boolean isChooseByOffset = true;
                if (isChoose) {
                    logger.info("座位：{}可选", dailyTrainSeat);
                    getSeatList.add(dailyTrainSeat);
                } else {
                    logger.info("座位：{}不可选", dailyTrainSeat);
                    continue;
                }
                // 根据offset计算座位
                if (CollUtil.isNotEmpty(offsets)) {
                    for (int i = 1; i < offsets.size(); i++) {
                        Integer offset = offsets.get(i);
                        int nextIndex = carriageSeatIndex + offset - 1;

                        if (nextIndex < 0 || nextIndex >= dailyTrainSeats.size()) {
                            logger.info("座位：{}超出范围，不可选", nextIndex);
                            isChooseByOffset = false;
                            break;
                        }

                        DailyTrainSeat trainSeatNext = dailyTrainSeats.get(nextIndex);
                        boolean isNextChoose = calSell(trainSeatNext, start, end);
                        if (isNextChoose) {
                            logger.info("座位：{}可选", trainSeatNext.getCarriageSeatIndex());
                            getSeatList.add(trainSeatNext);
                        } else {
                            logger.info("座位：{}不可选", trainSeatNext.getCarriageSeatIndex());
                            isChooseByOffset = false;
                            break;
                        }
                    }
                }
                if (!isChooseByOffset) {
                    continue;
                }
                // 保存选择座位
                finalChooseSeatList.addAll(getSeatList);
                logger.info("最终选座结果：{}", dailyTrainCarriage);
                return;
            }
        }
    }

    /**
     * 计算某座位在区间是否可卖
     *
     * @param dailyTrainSeat dailyTrainSeat
     */
    private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer start, Integer end) {
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(start, end);

        if (Integer.parseInt(sellPart) > 0) {
            logger.info("座位：{}在区间{}-{}不可选", dailyTrainSeat.getCarriageSeatIndex(), start, end);
            return false;
        } else {
            logger.info("座位：{}在区间{}-{}可选", dailyTrainSeat.getCarriageSeatIndex(), start, end);
            String curSell = sellPart.replaceAll("0", "1");
            curSell = StrUtil.fillBefore(curSell, '0', end);
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            int newSellBinInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            curSell = StrUtil.fillBefore(NumberUtil.getBinaryStr(newSellBinInt), '0', sell.length());
            logger.info("座位:{}被选中，原售票信息：{}，车站区间：{}~{},，既：{}，最终售票信息：{}",
                    dailyTrainSeat.getCarriageSeatIndex(), sell, start, end, sellPart, curSell);
            dailyTrainSeat.setSell(curSell);
            return true;
        }
    }

    private void sell(ConfirmOrder confirmOrder) {
        logger.info("更新订单处理信息，防止重复处理");
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
        updateStatus(confirmOrder);
        // 查询余票记录
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUniqueKey(confirmOrder.getDate(),
                confirmOrder.getTrainCode(), confirmOrder.getStart(),
                confirmOrder.getEnd());

        logger.info("查出余票记录：{}", dailyTrainTicket);
        List<ConfirmOrderTicketReq> tickets = JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class);
        deductTicketStock(dailyTrainTicket, tickets);

        // 最终选座结果
        List<DailyTrainSeat> finalChooseSeatList = new ArrayList<>();

        // 计算座位偏移值
        if (StrUtil.isNotBlank(tickets.get(0).getSeat())) {
            logger.info("本次购票有选座");
            List<SeatColEnum> colEnums = SeatColEnum.getColsByType(tickets.get(0).getSeatTypeCode());
            logger.info("本次选座的作为类型包含的列为：{}", colEnums);
            List<String> referList = new ArrayList<>();
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum : colEnums) {
                    referList.add(seatColEnum.getCode() + i);
                }
            }
            logger.info("用于做参照的两排座位：{}", referList);
            // 绝对偏移值
            List<Integer> aboluteOffsetList = new ArrayList<>();
            for (ConfirmOrderTicketReq ticket : tickets) {
                int seatIndex = referList.indexOf(ticket.getSeat());
                aboluteOffsetList.add(seatIndex);
            }
            // 计算相对偏移值

            List<Integer> offsetList = new ArrayList<>();
            for (Integer index : aboluteOffsetList) {
                int offset = index - aboluteOffsetList.get(0);
                offsetList.add(offset);
            }
            logger.info("相对偏移值：{}", offsetList);
            getSeat(finalChooseSeatList, confirmOrder.getDate(), confirmOrder.getTrainCode(), tickets.get(0).getSeatTypeCode(),
                    tickets.get(0).getSeat().split("")[0], offsetList, dailyTrainTicket.getStartIndex(), dailyTrainTicket.getEndIndex());
        } else {
            logger.info("本次购票没有选座");
            tickets.forEach(ticket -> {
                getSeat(finalChooseSeatList, confirmOrder.getDate(), confirmOrder.getTrainCode(),
                        ticket.getSeatTypeCode(), null, null,
                        dailyTrainTicket.getStartIndex(),
                        dailyTrainTicket.getEndIndex());
            });
        }
        // 保存 最终选座结果
        afterConfirmOrderService.afterDoConfirmOrder(dailyTrainTicket, finalChooseSeatList, tickets, confirmOrder);
    }

    @Override
    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
        ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        //     INIT("I", "初始"),
        //     PENDING("P", "处理中"),
        //     SUCCESS("S", "成功"),
        //     FAILURE("F", "失败"),
        //     EMPTY("E", "无票"),
        //     CANCEL("C", "取消"),
        int result = switch (statusEnum) {
            case INIT -> -999;
            case PENDING -> 0;
            case SUCCESS -> -1;
            case FAILURE -> -2;
            case EMPTY -> -3;
            case CANCEL -> -4;
        };

        if (result == -999) {
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.or().andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andDateEqualTo(confirmOrder.getDate())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime());
            confirmOrderExample.or().andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andDateEqualTo(confirmOrder.getDate());
            long counted = confirmOrderMapper.countByExample(confirmOrderExample);
            return Math.toIntExact(counted);
        }
        return result;
    }
}